/*
 * $Id: IconBorder.java 4147 2012-02-01 17:13:24Z kschaefe $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package org.jdesktop.swingx.border;

import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.io.Serializable;

import javax.swing.Icon;
import javax.swing.SwingConstants;
import javax.swing.border.Border;

import org.jdesktop.beans.JavaBean;
import org.jdesktop.swingx.icon.EmptyIcon;

/**
 * {@code IconBorder} creates a border that places an {@code Icon} in the border
 * on the horizontal axis. The border does not add any additional insets other
 * than the inset required to produce the space for the icon. If additional
 * insets are required, users should create a
 * {@link javax.swing.border.CompoundBorder compund border}.
 * <p>
 * This border is useful when attempting to add {@code Icon}s to pre-existing
 * components without requiring specialty painting.
 * 
 * @author Amy Fowler
 * @author Karl Schaefer
 * 
 * @version 1.1
 */
@JavaBean
public class IconBorder implements Border, Serializable {

	/**
	 * An empty icon.
	 */
	public static final Icon EMPTY_ICON = new EmptyIcon();
	private int padding;
	private Icon icon;
	private int iconPosition;
	private Rectangle iconBounds = new Rectangle();

	/**
	 * Creates an {@code IconBorder} with an empty icon in a trailing position
	 * with a padding of 4.
	 * 
	 * @see #EMPTY_ICON
	 */
	public IconBorder() {
		this(null);
	}

	/**
	 * Creates an {@code IconBorder} with the specified icon in a trailing
	 * position with a padding of 4.
	 * 
	 * @param validIcon
	 *            the icon to set. This may be {@code null} to represent an
	 *            empty icon.
	 * @see #EMPTY_ICON
	 */
	public IconBorder(Icon validIcon) {
		this(validIcon, SwingConstants.TRAILING);
	}

	/**
	 * Creates an {@code IconBorder} with the specified constraints and a
	 * padding of 4.
	 * 
	 * @param validIcon
	 *            the icon to set. This may be {@code null} to represent an
	 *            empty icon.
	 * @param iconPosition
	 *            the position to place the icon relative to the component
	 *            contents. This must be one of the following
	 *            {@code SwingConstants}:
	 *            <ul>
	 *            <li>{@code LEADING}</li>
	 *            <li>{@code TRAILING}</li>
	 *            <li>{@code EAST}</li>
	 *            <li>{@code WEST}</li>
	 *            </ul>
	 * @throws IllegalArgumentException
	 *             if {@code iconPosition} is not a valid position.
	 * @see #EMPTY_ICON
	 */
	public IconBorder(Icon validIcon, int iconPosition) {
		this(validIcon, iconPosition, 4);
	}

	/**
	 * Creates an {@code IconBorder} with the specified constraints. If
	 * {@code validIcon} is {@code null}, {@code EMPTY_ICON} is used instead. If
	 * {@code padding} is negative, then the border does not use padding.
	 * 
	 * @param validIcon
	 *            the icon to set. This may be {@code null} to represent an
	 *            empty icon.
	 * @param iconPosition
	 *            the position to place the icon relative to the component
	 *            contents. This must be one of the following
	 *            {@code SwingConstants}:
	 *            <ul>
	 *            <li>{@code LEADING}</li>
	 *            <li>{@code TRAILING}</li>
	 *            <li>{@code EAST}</li>
	 *            <li>{@code WEST}</li>
	 *            </ul>
	 * @param padding
	 *            the padding to surround the icon with. All non-positive values
	 *            set the padding to 0.
	 * @throws IllegalArgumentException
	 *             if {@code iconPosition} is not a valid position.
	 * @see #EMPTY_ICON
	 */
	public IconBorder(Icon validIcon, int iconPosition, int padding) {
		setIcon(validIcon);
		setPadding(padding);
		setIconPosition(iconPosition);
	}

	private boolean isValidPosition(int position) {
		boolean result = false;

		switch (position) {
			case SwingConstants.LEADING:
			case SwingConstants.TRAILING:
			case SwingConstants.EAST:
			case SwingConstants.WEST:
				result = true;
				break;
			default:
				result = false;
		}

		return result;
	}

	/**
	 * {@inheritDoc}
	 */
	public Insets getBorderInsets(Component c) {
		int horizontalInset = icon.getIconWidth() + (2 * padding);
		int iconPosition = bidiDecodeLeadingTrailing(c.getComponentOrientation(), this.iconPosition);
		if (iconPosition == SwingConstants.EAST) {
			return new Insets(0, 0, 0, horizontalInset);
		}
		return new Insets(0, horizontalInset, 0, 0);
	}

	/**
	 * Sets the icon for this border.
	 * 
	 * @param validIcon
	 *            the icon to set. This may be {@code null} to represent an
	 *            empty icon.
	 * @see #EMPTY_ICON
	 */
	public void setIcon(Icon validIcon) {
		this.icon = validIcon == null ? EMPTY_ICON : validIcon;
	}

	/**
	 * This border is not opaque.
	 * 
	 * @return always returns {@code false}
	 */
	public boolean isBorderOpaque() {
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
		int iconPosition = bidiDecodeLeadingTrailing(c.getComponentOrientation(), this.iconPosition);
		if (iconPosition == SwingConstants.NORTH_EAST) {
			iconBounds.y = y + padding;
			iconBounds.x = x + width - padding - icon.getIconWidth();
		} else if (iconPosition == SwingConstants.EAST) { // EAST
			iconBounds.y = y + ((height - icon.getIconHeight()) / 2);
			iconBounds.x = x + width - padding - icon.getIconWidth();
		} else if (iconPosition == SwingConstants.WEST) {
			iconBounds.y = y + ((height - icon.getIconHeight()) / 2);
			iconBounds.x = x + padding;
		}
		iconBounds.width = icon.getIconWidth();
		iconBounds.height = icon.getIconHeight();
		icon.paintIcon(c, g, iconBounds.x, iconBounds.y);
	}

	/**
	 * Returns EAST or WEST depending on the ComponentOrientation and the given
	 * postion LEADING/TRAILING this method has no effect for other position
	 * values
	 */
	private int bidiDecodeLeadingTrailing(ComponentOrientation c, int position) {
		if (position == SwingConstants.TRAILING) {
			if (!c.isLeftToRight()) {
				return SwingConstants.WEST;
			}
			return SwingConstants.EAST;
		}
		if (position == SwingConstants.LEADING) {
			if (c.isLeftToRight()) {
				return SwingConstants.WEST;
			}
			return SwingConstants.EAST;
		}
		return position;
	}

	/**
	 * Gets the padding surrounding the icon.
	 * 
	 * @return the padding for the icon. This value is guaranteed to be
	 *         nonnegative.
	 */
	public int getPadding() {
		return padding;
	}

	/**
	 * Sets the padding around the icon.
	 * 
	 * @param padding
	 *            the padding to set. If {@code padding < 0}, then
	 *            {@code padding} will be set to {@code 0}.
	 */
	public void setPadding(int padding) {
		this.padding = padding < 0 ? 0 : padding;
	}

	/**
	 * Returns the position to place the icon (relative to the component
	 * contents).
	 * 
	 * @return one of the following {@code SwingConstants}:
	 *         <ul>
	 *         <li>{@code LEADING}</li>
	 *         <li>{@code TRAILING}</li>
	 *         <li>{@code EAST}</li>
	 *         <li>{@code WEST}</li>
	 *         </ul>
	 */
	public int getIconPosition() {
		return iconPosition;
	}

	/**
	 * Sets the position to place the icon (relative to the component contents).
	 * 
	 * @param iconPosition
	 *            must be one of the following {@code SwingConstants}:
	 *            <ul>
	 *            <li>{@code LEADING}</li>
	 *            <li>{@code TRAILING}</li>
	 *            <li>{@code EAST}</li>
	 *            <li>{@code WEST}</li>
	 *            </ul>
	 * @throws IllegalArgumentException
	 *             if {@code iconPosition} is not a valid position.
	 */
	public void setIconPosition(int iconPosition) {
		if (!isValidPosition(iconPosition)) {
			throw new IllegalArgumentException("Invalid icon position");
		}
		this.iconPosition = iconPosition;
	}

}
